#include "error.h"
#include "string.h"

#include "fs/deffs.h"
#include "fs/device.h"
#include "fs/inode.h"
#include "fs/stat.h"

/**
 * @brief
 *
 * @param node
 * @param flags
 * @return int
 */
static int
dev_open(inode_t* node, uint32_t flags) {
    if (flags & (O_CREATE | O_TRUNC | O_EXCL | O_APPEND))
        return -E_INVAL;
    device_t* dev = vop_info(node, device);
    return dop_open(dev, flags);
}

static int
dev_close(inode_t* node) {
    device_t* dev = vop_info(node, device);
    return dop_close(dev);
}

static int
dev_read(inode_t* node, iobuf_t* iob) {
    device_t* dev = vop_info(node, device);
    return dop_io(dev, iob, 0);
}

static int
dev_write(inode_t* node, iobuf_t* iob) {
    device_t* dev = vop_info(node, device);
    return dop_io(dev, iob, 1);
}

static int
dev_ioctl(inode_t* node, int op, void* data) {
    device_t* dev = vop_info(node, device);
    return dop_ioctl(dev, op, data);
}

static int
dev_fstat(inode_t* node, stat_t* stat) {
    memset(stat, 0, sizeof(stat));
    int ret = vop_getType(node, &(stat->st_mode));
    if (ret != 0)
        return ret;
    device_t* dev = vop_info(node, device);
    stat->st_nlinks = 1;
    stat->st_blocks = dev->d_blocks;
    stat->st_size = dev->d_blocksize * dev->d_blocksize;
    return 0;
}

static int
dev_getType(inode_t* node, uint32_t* type_store) {
    device_t* dev = vop_info(node, device);
    *type_store = (dev->d_blocks > 0) ? S_IFBLK : S_IFCHR;
    return 0;
}

static int
dev_trySeek(inode_t* node, offset_t pos) {
    device_t* dev = vop_info(node, device);
    if ((dev->d_blocksize > 0) && (pos % dev->d_blocksize == 0) && (pos < dev->d_blocks * dev->d_blocksize))
        return 0;
    return -E_INVAL;
}

static int
dev_lookup(inode_t* node, char* path, inode_t** node_store) {
    if (path[0] != '\0')
        return -E_NOENTRY;
    vop_refInc(node);
    *node_store = node;
    return 0;
}

static const inodeOps_t dev_node_ops = {
    .vop_magic = VOP_MAGIC,
    .vop_open = dev_open,
    .vop_close = dev_close,
    .vop_read = dev_read,
    .vop_write = dev_write,
    .vop_fstat = dev_fstat,
    .vop_ioctl = dev_ioctl,
    .vop_getType = dev_getType,
    .vop_trySeek = dev_trySeek,
    .vop_lookup = dev_lookup,
};

#define init_device(x)              \
    extern void dev_init_##x(void); \
    dev_init_##x()

void
dev_init(void) {
    init_device(stdin);
    init_device(stdout);
    init_device(disk0);
}

inode_t*
dev_createInode(void) {
    inode_t* node = alloc_inode(device);
    if (node != NULL)
        vop_init(node, &dev_node_ops, NULL);
    return node;
}